Technote 1196Cursor Components |
CONTENTSPrerequisites and ResourcesLimitations Building Components
Using Components
Fighting Flicker Summary |
This Technote describes the building and use of cursor components. Cursor components allow you to build custom color cursors completely under your control. Unlike previous color cursors on the Macintosh, these components allow much more flexibility. Cursors created utilizing this component are pretty unlimited. They could be extra large, thousands or millions of colors, transparent, situationally intelligent, and/or animated. The design is constrained only by your imagination. Cursor components are built in two parts. The main part is the component itself, which can be built by expanding on the sample code sections provided in this Technote. The second part is the application that instantiates and controls the cursor. The second half of this technote will provide guidance on ways to do this. The last section of this technote will address cursor flicker and present a solution to the problem. |
Prerequisites and ResourcesThe Cursor Component SDK provides a sample application from which most of this Technotes code samples are taken. You should study this application and its cursor components carefully. These are very good starting points. When building an application, there will be two parts (at least): the application itself and the cursor component(s). The simplest way to put together the components is as code resources. To this end, the SDK sample projects provide a good starting point. (Duplicating the cursor component projects and working from there would seem to be the simplest approach.) The application is much simpler. A short study of the cursor code shows that it is very portable, and can be worked into almost any existing application. Developers will need Universal Interfaces 3.3 to develop cursor components. This is due to new functionality that has been added to QuickDraw headers and interfaces to support cursor components. One last note: cursor components are available only with Mac OS 9.0 and later; therefore, you should always have a fallback if you plan deployment on systems prior to Mac OS 9.0. Additionally, currently cursor components are not supported under Mac OS X and Carbon. This may change in the future, and this Note will be updated as necessary. |
LimitationsCurrently, on Mac OS 9, Cursor Components have a few limitations. Due to ATI RAGE 128 video hardware architecture the cursor can exhibit flicker. This flicker varies with different monitor settings and from system to system. See the Fighting Flicker for more information of preventing this problem. Additionally, Cursor Components do not function with PowerBooks due to adverse interactions with the "mouse trails" feature. There is no workaround, although this may be fixed in the future. Finally, all cursor drawing and erasing happens at interrupt time. (This will be discussed later in this Technote.) Thus, you should use particular care in designing the drawing and erasing portion of the component, relying only on interrupt-safe functions for these sections. The Interrupt-Safe Routines Technote is a good source for this information. |
struct CursorGlobalsRecord { ComponentInstance self; Rect bounds; Point hotSpot; UInt32 animIndex; UInt32 deviceCount; CursorDeviceRecord deviceList[12]; // fixed 12 displays max }; typedef struct CursorGlobalsRecord CursorGlobalsRecord; typedef CursorGlobalsRecord * CursorGlobalsPtr; typedef CursorGlobalsPtr * CursorGlobalsHnd; // ------------------------------------------------------------------- // Routine descriptors RoutineDescriptor ComponentCanDoRD = BUILD_ROUTINE_DESCRIPTOR (uppCallComponentCanDoProcInfo, (ProcPtr)ComponentCanDo); RoutineDescriptor ComponentGetVersionRD = BUILD_ROUTINE_DESCRIPTOR (uppCallComponentVersionProcInfo, (ProcPtr)ComponentGetVersion); // Describe the main entry RoutineDescriptor mainRD = BUILD_ROUTINE_DESCRIPTOR (uppComponentRoutineProcInfo, main); pascal ComponentResult main (ComponentParameters *params,char **storage) { ComponentResult result = noErr; // If the selector is less than zero, it's a Component manager selector. if (params->what < 0) { switch (params->what) { case kComponentOpenSelect: return CallWithStoragePI(storage, params, ComponentOpen, uppCallComponentOpenProcInfo); case kComponentCloseSelect: return CallWithStoragePI(storage, params, ComponentClose, uppCallComponentCloseProcInfo); case kComponentCanDoSelect: return CallComponentFunction(params, &ComponentCanDoRD); case kComponentVersionSelect: return CallComponentFunction(params, &ComponentGetVersionRD); default: return noErr; } } switch (params->what) { case kCursorComponentInit: return CallWithStoragePI(storage, params, CursorInitialize, uppCursorInitProcInfo); case kCursorComponentGetInfo: return CallWithStoragePI(storage, params, GetCursorInfo, uppGetCursorInfoProcInfo); case kCursorComponentSetData: return CallWithStoragePI(storage, params, SetCursorData, uppSetCursorDataProcInfo); case kCursorComponentReconfigure: return CallWithStoragePI(storage, params, CursorReconfigure, uppCursorReconfigureProcInfo); case kCursorComponentDraw: return CallWithStoragePI(storage, params, CursorDraw, uppCursorDrawProcInfo); case kCursorComponentErase: return CallWithStoragePI(storage, params, CursorErase, uppCursorEraseProcInfo); case kCursorComponentMove: return CallWithStoragePI(storage, params, CursorMove, uppCursorMoveProcInfo); case kCursorComponentAnimate: return CallWithStoragePI(storage, params, CursorAnimate, uppCursorAnimateProcInfo); default: result = unimpErr; break; } return result; } pascal ComponentResult ComponentOpen (CursorGlobalsHnd storage, ComponentInstance self) { #pragma unused (storage) ComponentResult result = noErr; storage = (CursorGlobalsHnd)NewHandleClear(sizeof(CursorGlobalsRecord)); if (storage != nil ) { SetComponentInstanceStorage(self, (Handle)storage); // keep an instance of ourself (*storage)->self = self; } else result = MemError(); return result; } pascal ComponentResult ComponentClose (CursorGlobalsHnd storage, ComponentInstance self) { if (storage != nil) { DisposeDeviceRecords(storage); DisposeHandle((Handle)storage); } return(noErr); } pascal ComponentResult ComponentCanDo (short selector) { switch (selector) { case kComponentOpenSelect: case kComponentCloseSelect: case kComponentCanDoSelect: case kComponentVersionSelect: case kCursorComponentInit: case kCursorComponentGetInfo: case kCursorComponentSetData: case kCursorComponentReconfigure: case kCursorComponentDraw: case kCursorComponentErase: case kCursorComponentMove: return true; default: return false; } } /* Version information */ #define CURSOR_COMPONENT_REV 1 #define kCursorComponentVersion 1 pascal ComponentResult ComponentGetVersion (void) { // Cursor Component interface version in hi word, // code rev in lo word return ((kCursorComponentVersion << 16) | CURSOR_COMPONENT_REV); } |
The second section of the component is the cursor component functions. This consists of eight basic functions that handle the main processing, drawing and erasing of your cursor. We will cover each functional area in detail, but you are advised to look closely at the examples provided for basic structure and functionality. CursorInitialize
This function should create a cursor image, mask and save under buffers, and other info for each device. It will be called once when the component is opened and again when display devices are reconfigured or removed. The function |
pascal ComponentResult CursorInitialize (CursorGlobalsHnd storage) { ComponentResult result; Rect theRect = {0, 0, 16, 16}; Point hotSpot = {0, 0}; if (storage != nil) { result = InitializeCursorFlush(); if (result == noErr) { (*storage)->bounds = theRect; (*storage)->hotSpot = hotSpot; (*storage)->deviceCount = 0; result = CreateDeviceRecords(storage); } } return result; } static OSErr CreateDeviceRecords (CursorGlobalsHnd cursorGlobals) { OSErr err; GDHandle theDevice; GWorldPtr theWorld; PrivateCursorData data; UInt32 counter; SInt16 depth; Rect bounds; short index; bounds = (*cursorGlobals)->bounds; // start with the first device theDevice = DMGetFirstScreenDevice(true); while (theDevice != nil) { counter = (*cursorGlobals)->deviceCount; depth = (*(*theDevice)->gdPMap)->pixelSize; (*cursorGlobals)->deviceList[counter].device = nil; (*cursorGlobals)->deviceList[counter].saveUnder = nil; for (index = 0; index < kNumberOfAnimations; index++) { (*cursorGlobals)->deviceList[counter].cursorImage[index] = nil; // create GWorlds to hold images of cursor expanded to the bitdepth // of this device err = CreateImageWorld(&theWorld, depth, &bounds, theDevice); if (err == noErr) (*cursorGlobals)->deviceList[counter].cursorImage[index]= theWorld; } if (err == noErr) { // create a GWorld to hold mask err = CreateImageWorld(&theWorld, depth, &bounds, theDevice); if (err == noErr && theWorld != nil) { (*cursorGlobals)->deviceList[counter].maskImage = theWorld; // create a GWorld to hold saved screen image err = CreateImageWorld(&theWorld, depth, &bounds, theDevice); if (err == noErr && theWorld != nil) { (*cursorGlobals)->deviceList[counter].saveUnder = theWorld; (*cursorGlobals)->deviceList[counter].device = theDevice; // increment number of instances counter (*cursorGlobals)->deviceCount++; } } } theDevice = DMGetNextScreenDevice(theDevice, true); } if (err != noErr) { for (index = 0; index < kNumberOfAnimations; index++) { if ((*cursorGlobals)->deviceList[counter].cursorImage[index] != nil) DisposeGWorld ((*cursorGlobals)->deviceList[counter].cursorImage[index]); } if ((*cursorGlobals)->deviceList[counter].maskImage != nil) DisposeGWorld((*cursorGlobals)->deviceList[counter].maskImage); if ((*cursorGlobals)->deviceList[counter].saveUnder != nil) DisposeGWorld((*cursorGlobals)->deviceList[counter].saveUnder); } else { // fill out the cursor data err = SetCursorData(cursorGlobals, &data); } return err; } |
This function fills out the |
pascal ComponentResult GetCursorInfo (CursorGlobalsHnd storage, CursorInfo *info) { OSErr err = noErr; if (info != nil) { info->version = kCursorComponentsVersion; // use zero for minimal capabilities info->capabilities = cursorDoesAnimate // number of ticks between animations (zero for none) info->animateDuration = 15; info->bounds = (*storage)->bounds; info->hotspot = (*storage)->hotSpot; } else err = paramErr; return err; } |
SetCursorData
This function enables an application to pass data to the cursor component. The first parameter is a handle to the |
pascal ComponentResult SetCursorData (CursorGlobalsHnd storage, PrivateCursorPtr data) { ComponentResult result = -1; PicHandle cursorPict; PicHandle maskPict; GWorldPtr imageWorld; GWorldPtr oldPort; GDHandle oldGD; Rect theRect; short resFile; short c; short index; // hide the cursor since we are going to change it HideCursor(); // wack the values to those known by cursor so demo app works data->pictureID = kCursorPictureID; data->hotSpot.h = (short)(((*storage)->bounds.right - (*storage)->bounds.left) / 2); data->hotSpot.v = (short)(((*storage)->bounds.bottom - (*storage)->bounds.top) / 2); resFile = OpenComponentResFile((Component)(*storage)->self); if (resFile != -1) { cursorPict = GetPicture((short)data->pictureID); maskPict = GetPicture((short)(data->pictureID + kNumberOfAnimations)); if (cursorPict != nil && maskPict != nil) { theRect = (*storage)->bounds; GetGWorld(&oldPort, &oldGD); for (c = 0; c < (*storage)->deviceCount; c++) { for (index = 0; index < kNumberOfAnimations; index++) { imageWorld = (*storage)->deviceList[c].cursorImage[index]; cursorPict = GetPicture((short)(data->pictureID + index)); if (imageWorld != nil && cursorPict != nil) { SetGWorld(imageWorld, nil); DrawPicture(cursorPict, &theRect); } } imageWorld = (*storage)->deviceList[c].maskImage; if (imageWorld != nil) { SetGWorld(imageWorld, nil); DrawPicture(maskPict, &theRect); } } SetPort((GrafPtr)oldPort); SetGDevice(oldGD); (*storage)->hotSpot = data->hotSpot; // Now tell Quickdraw we changed result = CursorComponentChanged((*storage)->self); } CloseComponentResFile(resFile); } // show the new cursor ShowCursor(); return result; } |
CursorReconfigureThis function is called when the graphics environment has changed. The component should now rebuild its cursors (if required) to correspond to the new environment. A simple example implementation is below. This example calls |
pascal ComponentResult CursorReconfigure (CursorGlobalsHnd storage) { ComponentResult result; if (storage != nil) { // dispose of old device records if they exist if ((*storage)->deviceCount > 0) DisposeDeviceRecords(storage); result = CreateDeviceRecords(storage); } return result; } static void DisposeDeviceRecords (CursorGlobalsHnd cursorGlobals) { SInt16 index; SInt16 cursorIndex; for (index = 0; index < (*cursorGlobals)->deviceCount; index++) { (*cursorGlobals)->deviceList[index].device = nil; for (cursorIndex = 0; cursorIndex < kNumberOfAnimations; cursorIndex++) { if ((*cursorGlobals)->deviceList[index].cursorImage[cursorIndex] != nil) DisposeGWorld ((*cursorGlobals)->deviceList[index].cursorImage[cursorIndex]); } if ((*cursorGlobals)->deviceList[index].saveUnder != nil) { DisposeGWorld((*cursorGlobals)->deviceList[index].saveUnder); (*cursorGlobals)->deviceList[index].saveUnder = nil; } } (*cursorGlobals)->deviceCount = 0; } |
CursorDraw
This is the core of the cursor component, and it will be called every time the cursor is drawn for every display that the cursor spans. Additionally, since cursors are drawn at interrupt time, this function must not move memory (call Memory Manager routines). The first parameter, as usual, is a handle to Once the cursor data is found and you have the display and point, the cursor needs to be drawn. It is key to note that this entire drawing sequence occurs at interrupt time; it should be quick and use only interrupt-safe routines. Additionally, the plotted cursor must be clipped to the appropriate device. Lastly, the plotting code absolutely must save the data underneath the cursor image prior to plotting the new one. This can be done all at once, or pixel-by-pixel, as shown in the example. |
pascal ComponentResult CursorDraw (CursorGlobalsHnd storage, GDHandle device, Point *position) { SInt16 index; index = FindDeviceRecordIndex(storage, device); if (index != -1) { PlotMaskedImageWithSave (device, *position, (*storage)->deviceList[index].cursorImage [(*storage)->animIndex]->portPixMap, (*storage)->deviceList[index].maskImage->portPixMap, (*storage)->deviceList[index].saveUnder->portPixMap); } return noErr; } static SInt16 FindDeviceRecordIndex (CursorGlobalsHnd cursorGlobals, GDHandle findDevice) { UInt16 index; for (index = 0; index < (*cursorGlobals)->deviceCount; index++) { if ((*cursorGlobals)->deviceList[index].device == findDevice) return index; } // didn't find device return -1; } static void PlotMaskedImageWithSave (GDHandle device, Point mouse, PixMapHandle srcPix, PixMapHandle mskPix, PixMapHandle savePix) { Rect theRect; UInt32 h; UInt32 v; UInt32 srcHStart; UInt32 srcVStart; UInt32 offRowBytes; UInt32 dstRowBytes; SInt32 srcHeight; SInt32 srcWidth; UInt32 deviceHeight; UInt32 deviceWidth; UInt32 offOffset; SInt32 xOffset; SInt32 yOffset; UInt16 depth; Ptr srcBase; Ptr mskBase; Ptr dstBase; Ptr saveBase; Ptr srcData; Ptr mskData; Ptr dstData; Ptr saveData; // get base of src, msk, and save srcBase = (*srcPix)->baseAddr; mskBase = (*mskPix)->baseAddr; saveBase = (*savePix)->baseAddr; offRowBytes = (*srcPix)->rowBytes & 0x7FFF; // get base and rowbytes of destination dstBase = (*(*device)->gdPMap)->baseAddr; dstRowBytes = (*(*device)->gdPMap)->rowBytes & 0x7FFF; depth = (*(*device)->gdPMap)->pixelSize; theRect = (*device)->gdRect; deviceWidth = theRect.right - theRect.left; deviceHeight = theRect.bottom - theRect.top; theRect = (*srcPix)->bounds; srcWidth = theRect.right - theRect.left; srcHeight = theRect.bottom - theRect.top; xOffset = mouse.h; yOffset = mouse.v; srcHStart = 0; srcVStart = 0; // trim h if (xOffset < 0) { srcWidth -= -xOffset; srcHStart = -xOffset; xOffset = 0; } else { if ((xOffset + srcWidth) > deviceWidth) srcWidth = deviceWidth - xOffset; } // trim y if (yOffset < 0) { srcHeight -= -yOffset; srcVStart = -yOffset; yOffset = 0; } else { if ((yOffset + srcHeight) > deviceHeight) srcHeight = deviceHeight - yOffset; } switch (depth) { case 1: case 2: case 4: case 8: DebugStr("\pDon't handle indexed yet"); break; case 16: // need to align this stuff { UInt16 *srcPixel; UInt16 *mskPixel; UInt16 *dstPixel; UInt16 *savePixel; offOffset = (srcVStart * offRowBytes) + (srcHStart << 1); srcData = srcBase + offOffset; mskData = mskBase + offOffset; saveData = saveBase + offOffset; dstData = dstBase + (yOffset * dstRowBytes) + (xOffset << 1); for (v = 0; v < srcHeight; v++) { srcPixel = (UInt16 *)srcData; mskPixel = (UInt16 *)mskData; dstPixel = (UInt16 *)dstData; savePixel = (UInt16 *)saveData; for (h = 0; h < srcWidth; h++) { if (*mskPixel++ == 0x00) { *savePixel = *dstPixel; *dstPixel = *srcPixel; } dstPixel++; srcPixel++; savePixel++; } srcData += offRowBytes; mskData += offRowBytes; dstData += dstRowBytes; saveData += offRowBytes; } } break; case 32: { UInt32 *srcPixel; UInt32 *mskPixel; UInt32 *dstPixel; UInt32 *savePixel; offOffset = (srcVStart * offRowBytes) + (srcHStart << 2); srcData = srcBase + offOffset; mskData = mskBase + offOffset; saveData = saveBase + offOffset; dstData = dstBase + (yOffset * dstRowBytes) + (xOffset << 2); for (v = 0; v < srcHeight; v++) { srcPixel = (UInt32 *)srcData; mskPixel = (UInt32 *)mskData; dstPixel = (UInt32 *)dstData; savePixel = (UInt32 *)saveData; for (h = 0; h < srcWidth; h++) { if (*mskPixel++ == 0x00) { *savePixel = *dstPixel; *dstPixel = *srcPixel; } srcPixel++; dstPixel++; savePixel++; } srcData += offRowBytes; mskData += offRowBytes; saveData += offRowBytes; dstData += dstRowBytes; } } break; default: break; } CursorFlush(device); } |
CursorErase
This function really is a drawing function. It just undoes the work done by |
pascal ComponentResult CursorErase (CursorGlobalsHnd storage, GDHandle device, Point *position) { SInt16 index; index = FindDeviceRecordIndex(storage, device); if (index != -1) { RestoreMaskedImage(device, *position, (*storage)->deviceList[index].saveUnder->portPixMap, (*storage)->deviceList[index].maskImage->portPixMap); } return noErr; } static void RestoreMaskedImage (GDHandle device, Point mouse, PixMapHandle srcPix, PixMapHandle mskPix) { Rect theRect; UInt32 h; UInt32 v; UInt32 srcHStart; UInt32 srcVStart; UInt32 offRowBytes; UInt32 dstRowBytes; UInt32 offOffset; SInt32 srcHeight; SInt32 srcWidth; UInt32 deviceHeight; UInt32 deviceWidth; SInt32 xOffset; SInt32 yOffset; UInt16 depth; Ptr srcBase; Ptr dstBase; Ptr mskBase; Ptr srcData; Ptr dstData; Ptr mskData; // get base of src and mask srcBase = (*srcPix)->baseAddr; mskBase = (*mskPix)->baseAddr; offRowBytes = (*srcPix)->rowBytes & 0x7FFF; // get base and rowbytes of destination dstBase = (*(*device)->gdPMap)->baseAddr; dstRowBytes = (*(*device)->gdPMap)->rowBytes & 0x7FFF; depth = (*(*device)->gdPMap)->pixelSize; theRect = (*device)->gdRect; deviceWidth = theRect.right - theRect.left; deviceHeight = theRect.bottom - theRect.top; theRect = (*srcPix)->bounds; srcWidth = theRect.right - theRect.left; srcHeight = theRect.bottom - theRect.top; xOffset = mouse.h; yOffset = mouse.v; srcHStart = 0; srcVStart = 0; // trim h if (xOffset < 0) { srcWidth -= -xOffset; srcHStart = -xOffset; xOffset = 0; } if ((xOffset + srcWidth) > deviceWidth) srcWidth = deviceWidth - xOffset; // trim y if (yOffset < 0) { srcHeight -= -yOffset; srcVStart = -yOffset; yOffset = 0; } if ((yOffset + srcHeight) > deviceHeight) srcHeight = deviceHeight - yOffset; switch (depth) { case 1: case 2: case 4: case 8: DebugStr("\pDon't handle indexed yet"); break; case 16: // need to align this stuff { UInt16 *srcPixel; UInt16 *dstPixel; UInt16 *mskPixel; offOffset = (srcVStart * offRowBytes) + (srcHStart << 1); srcData = srcBase + offOffset; mskData = mskBase + offOffset; dstData = dstBase + (yOffset * dstRowBytes) + (xOffset << 1); for (v = 0; v < srcHeight; v++) { srcPixel = (UInt16 *)srcData; dstPixel = (UInt16 *)dstData; mskPixel = (UInt16 *)mskData; for (h = 0; h < srcWidth; h++) { if (*mskPixel == 0x00) { *dstPixel = *srcPixel; } dstPixel++; mskPixel++; srcPixel++; } srcData += offRowBytes; mskData += offRowBytes; dstData += dstRowBytes; } } break; case 32: { UInt32 *srcPixel; UInt32 *dstPixel; UInt32 *mskPixel; offOffset = (srcVStart * offRowBytes) + (srcHStart << 2); srcData = srcBase + offOffset; mskData = mskBase + offOffset; dstData = dstBase + (yOffset * dstRowBytes) + (xOffset << 2); for (v = 0; v < srcHeight; v++) { srcPixel = (UInt32 *)srcData; dstPixel = (UInt32 *)dstData; mskPixel = (UInt32 *)mskData; for (h = 0; h < srcWidth; h++) { if (*mskPixel == 0x00) { *dstPixel = *srcPixel; } dstPixel++; mskPixel++; srcPixel++; } srcData += offRowBytes; mskData += offRowBytes; dstData += dstRowBytes; } } break; default: break; } } |
CursorMove
This function must both erase and restore the last image, and draw a new image. To this end, both the old position and new position are provided. Again, this all happens at interrupt time; the |
pascal ComponentResult CursorMove (CursorGlobalsHnd storage, GDHandle device, Point *lastPosition, Point *newPosition) { SInt16 index; index = FindDeviceRecordIndex(storage, device); if (index != -1) { RestoreMaskedImage(device, *lastPosition, (*storage)->deviceList[index].saveUnder->portPixMap, (*storage)->deviceList[index].maskImage->portPixMap); PlotMaskedImageWithSave(device, *newPosition, (*storage)->deviceList[index].cursorImage [(*storage)->animIndex]->portPixMap, (*storage)->deviceList[index].maskImage->portPixMap, (*storage)->deviceList[index].saveUnder->portPixMap); } return noErr; } |
CursorAnimate
The last function in the component API is |
pascal ComponentResult CursorAnimate (CursorGlobalsHnd storage, GDHandle device, Point *lastPosition) { #pragma unused (device, lastPosition) UInt32 index; if (device == GetMainDevice()) { index = (*storage)->animIndex; index++; if (index > kNumberOfAnimations - 1) index = 0; (*storage)->animIndex = index; } return noErr; } |
Resources
The cursor component compiled as a code resource needs a |
#define kCursorResID 129 #define kCursorName "Beachball Cursor" #define kCursorType 'curs' #define kCursorSubType 'bbcc' #define kCursorManufacturer 'appl' resource 'thng' (kCursorResID, kCursorName, purgeable) { kCursorType, kCursorSubType, kCursorManufacturer, 0x80000020, kAnyComponentFlagsMask, 'crco', kCursorResID, 'STR ', kCursorResID + 5000, 'STR ', kCursorResID + 5050, 'ICON', kCursorResID, 0x0, 8, 0, { /* array ComponentPlatformInfo: 1 elements */ /* [1] */ 0x80000000, 'crco', kCursorResID, platformPowerPC } }; |
OSErr RegisterCursorComponents (void) { OSErr err = -1; if (RegisterComponentResourceFile (CurResFile(), 0)) err = noErr; return err; } |
Opening the Cursor
Once the component is registered, we can open the cursor. This is achieved by setting up a |
OSErr OpenCursorComponent (Component c, ComponentInstance * ci); |
The |
void OpenTheCursor (OSType subType) { OSErr err; ComponentDescription theDesc; Component comp = nil; Point hotspot; // set up the descriptor theDesc.componentType = 'curs'; theDesc.componentSubType = subType; theDesc.componentManufacturer = 'appl'; theDesc.componentFlags = 0L; theDesc.componentFlagsMask = 0L; comp = FindNextComponent(0L, &theDesc); if (comp != nil) { err = OpenCursorComponent(comp, &gComp); if (err == noErr) gCurrentSubType = subType; else gComp = nil; if (err != noErr) HandleError(err, false); } } |
Setting the Cursor
After the cursor is opened it can be set. This should be done when the application wants to display the particular cursor. To achieve this |
OSErr SetCursorComponent (ComponentInstance ci); |
This can be accomplished in the following simple manner.
void SetTheCursor (void) { SetCursorComponent(gComp); } |
Closing the Cursor
Once the application is done with a particular cursor (and absolutely before exit), it should be close with |
OSErr CloseCursorComponent (ComponentInstance ci); |
Again, this is simply implemented in the sample by just checking for |
void CloseTheCursor (void) { if (gComp != nil) { CloseCursorComponent(gComp); gComp = nil; } } |
Modifying the Cursor
Cursor components possess the |
OSErr CursorComponentSetData (ComponentInstance ci, long data); |
This allows passing either direct data or a pointer to the cursor itself. An example of utilizing can also be found in the example application's |
OSErr SetCursorPictureID (short pictureID, Point *hotspot) { OSErr err = noErr; PrivateCursorData data; static short lastID = -99; // return if image is already set if (lastID == pictureID) return err; // only set image if cursor is bitmap cursor... if (gCurrentSubType == kBitMapSubType) { data.pictureID = (long)pictureID; data.hotSpot = *hotspot; err = CursorComponentSetData((ComponentInstance)gComp, (long)&data); if (err == noErr) lastID = pictureID; } return err; } |
void OpenNewCloseOld (OSType subType) { ComponentInstance oldCursor; // save the current cursor oldCursor = gComp; // open the new one OpenTheCursor(subType); if (oldCursor != nil) { CloseCursorComponent(oldCursor); } } |